//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.planw.beetl.sql;

import com.google.common.collect.Lists;
import com.intellij.codeInsight.completion.CompletionContributor;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionProvider;
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.VisualPosition;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.patterns.PatternCondition;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.patterns.PsiElementPattern;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiLanguageInjectionHost;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiReferenceParameterList;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeElement;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageManagerImpl;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.ui.JBColor;
import com.intellij.util.Consumer;
import com.intellij.util.PlatformIcons;
import com.intellij.util.ProcessingContext;
import com.planw.beetl.model.TreeNode;
import com.planw.beetl.service.BeetlMapperService;
import com.planw.beetl.service.BeetlSqlService;
import com.planw.beetl.utils.BeetlSqlConst;
import com.planw.beetl.utils.ConstUtil;
import com.planw.beetl.utils.MavenUtil;
import com.planw.beetl.utils.PsiKt;
import com.planw.beetl.utils.StrUtil;

import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.intellij.plugins.markdown.lang.MarkdownElementTypes;
import org.intellij.plugins.markdown.lang.MarkdownLanguage;
import org.intellij.plugins.markdown.lang.psi.impl.MarkdownCodeBlock;
import org.intellij.plugins.markdown.lang.psi.impl.MarkdownCodeFence;
import org.intellij.plugins.markdown.lang.psi.impl.MarkdownParagraph;
import org.jetbrains.annotations.NotNull;

public class BeetlSqlCompletionContributor extends CompletionContributor {
  private static final Logger logger = Logger.getInstance(BeetlSqlCompletionContributor.class);

  public BeetlSqlCompletionContributor() {
    PsiElementPattern.Capture<PsiElement> pattern = (PsiElementPattern.Capture)PlatformPatterns.psiElement().with(new MarkdownBeetlSqlPatternCondition());
    this.extend((CompletionType)null, pattern, new BeetlSqlCompletionProvider());
  }

  static class MarkdownBeetlSqlPatternCondition extends PatternCondition<PsiElement> {
    private static final Logger logger = Logger.getInstance(MarkdownBeetlSqlPatternCondition.class);

    public MarkdownBeetlSqlPatternCondition() {
      super(" BeetlSqlPatternCondition ");
    }

    @Override
    public boolean accepts(@NotNull PsiElement psiElement, ProcessingContext processingContext) {
      if (psiElement == null) {
        return false;
      }

      if (!ConstUtil.isReady()) {
        logger.info("项目未准备完成");
        return false;
      } else {
        Project project = psiElement.getProject();
        Module module = ModuleUtil.findModuleForFile(psiElement.getContainingFile().getOriginalFile().getVirtualFile(), project);
        if (!MavenUtil.isDependOnBeetlSql(module)) {
          logger.info(project.getName() + "项目无beetlsql依赖库，无需代码提示");
          return false;
        } else {
          PsiFile psiFile = psiElement.getContainingFile();
          String content = psiElement.getText();
          BeetlSqlConst beetlSqlConst = ConstUtil.getBeetlSqlConst(psiFile);
          boolean isContainBeetl = StrUtil.containAny(content, new String[]{beetlSqlConst.DELIMITER_STATEMENT_START, beetlSqlConst.DELIMITER_PLACEHOLDER_START, beetlSqlConst.DELIMITER_PLACEHOLDER_START2});
          if (!isContainBeetl) {
            logger.info("未包括BeetlSql的定界符和占位符");
            return false;
          } else {
            logger.info("判断内容[" + content + "]是否需要代码完成");
            InjectedLanguageManagerImpl ilm = InjectedLanguageManagerImpl.getInstanceImpl(project);
            boolean injectedFragment = ilm.isInjectedFragment(psiFile);
            Language fileLanguage;
            PsiElement parent;
            if (injectedFragment) {
              logger.info("属于内嵌语言");
              PsiFile topLevelFile = ilm.getTopLevelFile(psiElement);
              fileLanguage = topLevelFile.getOriginalFile().getLanguage();
              PsiLanguageInjectionHost injectionHost = ilm.getInjectionHost(psiElement);
              parent = injectionHost != null ? injectionHost.getOriginalElement() : null;
            } else {
              logger.info("属于非内嵌语言");
              fileLanguage = PsiUtilCore.findLanguageFromElement(psiElement);
              parent = psiElement.getParent();
            }

            boolean flag = fileLanguage.is(MarkdownLanguage.INSTANCE) && (parent instanceof MarkdownParagraph || parent instanceof MarkdownCodeFence || parent instanceof MarkdownCodeBlock);
            logger.info("判断结果：" + flag);
            return flag;
          }
        }
      }
    }
  }

  static class BeetlSqlCompletionProvider extends CompletionProvider<CompletionParameters> {
    private static final Logger logger = Logger.getInstance(BeetlSqlCompletionProvider.class);

    BeetlSqlCompletionProvider() {
    }

    @Override
    protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) {
      if (parameters == null) {
        return;
      }

      if (context == null) {
        return;
      }

      if (result == null) {
        return;
      }

      if (!ConstUtil.isReady()) {
        logger.info("项目未准备完成");
      } else {
        PsiFile currentPsiFile = parameters.getOriginalFile();
        BeetlSqlConst beetlSqlConst = ConstUtil.getBeetlSqlConst(currentPsiFile);
        String word = (String)Optional.of(parameters).map(CompletionParameters::getOriginalPosition).map(PsiElement::getText).orElse("");
        Editor editor = parameters.getEditor();
        Caret caret = editor.getCaretModel().getPrimaryCaret();
        VisualPosition visualPosition = caret.getVisualPosition();
        int column = visualPosition.column;
        word = StrUtil.findBeetlSqlVarSubStr(word, column, beetlSqlConst);
        if (StringUtil.isEmptyOrSpaces(word)) {
          logger.info("未找到正在输入的变量，跳过代码完成");
        } else {
          Project project = currentPsiFile.getProject();
          PsiElement positionPsi = parameters.getPosition();
          BeetlSqlService beetlSqlService = BeetlSqlService.getInstance(project);
          InjectedLanguageManagerImpl ilm = InjectedLanguageManagerImpl.getInstanceImpl(project);
          boolean injectedFragment = ilm.isInjectedFragment(currentPsiFile);
          logger.info("本次代码完成是否是内嵌代码段：" + injectedFragment);
          PsiElement curNode;
          if (injectedFragment) {
            curNode = ilm.getInjectionHost(positionPsi);
          } else {
            curNode = positionPsi;
          }

          String sqlId = this.findSqlId(curNode);
          logger.info("所属的SqlId：" + sqlId);
          if (!StringUtils.isBlank(sqlId)) {
            PsiFile mdPsiFile = ilm.getTopLevelFile(positionPsi);
            PsiClass mapper = beetlSqlService.findMapperClass(mdPsiFile.getOriginalFile());
            logger.info(String.format("通过SqlId【%s】找到mapper类【%s】 ", sqlId, mapper));
            if (mapper != null) {
              PsiMethod[] methods = mapper.findMethodsByName(sqlId, true);
              if (ArrayUtils.isEmpty(methods)) {
                logger.warn(String.format("没有找到匹配SqlId【%s】的mapper方法 ", sqlId));
                return;
              }

              logger.info("对文本[" + word + "]代码完成");
              if (StringUtils.endsWith(word, ".")) {
                word = word + "*";
              }

              String[] searchStrs = word.split("\\.");
              if (ArrayUtils.isNotEmpty(searchStrs)) {
                result = result.withPrefixMatcher(searchStrs[searchStrs.length - 1]);
                logger.info("进入匹配过程之前 ");
                this.addResultSet(currentPsiFile, result, methods, searchStrs);
                logger.info("进入匹配过程之后");

                  for (Map.Entry<String, LookupElement> entry : ConstUtil.DEFAULT_FUNC_CACHE.entrySet()) {
                      LookupElement value = entry.getValue();
                      result.addElement(value);
                  }
                  for (Map.Entry<String, LookupElement> entry : ConstUtil.EXT_FUNC_CACHE.entrySet()) {
                      LookupElement v = entry.getValue();
                      result.addElement(v);
                  }
              }
            }
          }
        }
      }
    }

    private String findSqlId(PsiElement curNode) {
      logger.info("【beetlsql】 findSqlId before curNode" + curNode);

      do {
        IElementType parentElementType = (IElementType)Optional.ofNullable(curNode).map(PsiElement::getParent).map(PsiElement::getNode).map(ASTNode::getElementType).orElse(null);
        if (MarkdownElementTypes.MARKDOWN_FILE_ELEMENT_TYPE.equals(parentElementType)) {
          logger.info("【beetlsql】 findSqlId after curNode" + curNode);
          return (String)Optional.ofNullable(curNode).map((x) -> PsiTreeUtil.findSiblingBackward(x, MarkdownElementTypes.SETEXT_1, (Consumer)null)).map(PsiTreeUtil::firstChild).map(PsiTreeUtil::firstChild).map(PsiElement::getText).orElse(null);
        }

        curNode = (PsiElement)Optional.ofNullable(curNode).map(PsiElement::getParent).orElse(null);
      } while(curNode != null);

      return null;
    }

    private void addResultSet(PsiFile psiFile, CompletionResultSet result, PsiMethod[] methods, String[] searchStrs) {
      BeetlMapperService beetlMapperService = BeetlMapperService.getInstance(psiFile.getProject());
      BeetlSqlConst beetlSqlConst = ConstUtil.getBeetlSqlConst(psiFile);
      String rootAnnotationClassName = beetlSqlConst.ROOT_BEETL_CLASS;
      String pageQueryClassName = beetlSqlConst.PAGE_QUERY_BEETL_CLASS;
      String paramAnnotationClassName = beetlSqlConst.PARAM_BEETL_CLASS;
      TreeNode root = new TreeNode();

      for(PsiMethod method : methods) {
        PsiParameter[] psiParameters = method.getParameterList().getParameters();
        int parameterSize = psiParameters.length;

        for(PsiParameter parameter : psiParameters) {
          if (!beetlMapperService.isPageRequest(pageQueryClassName, parameter)) {
            Queue<String> searchStrsQueue = new ArrayBlockingQueue(searchStrs.length, false, Arrays.asList(searchStrs));
            TreeNode child = new TreeNode();
            root.getChildren().add(child);
            if (searchStrsQueue.size() == 1 && PsiKt.isPrimitive(parameter.getType())) {
              String search = (String)searchStrsQueue.poll();
              this.addPrimitiveParameter(child, parameter, search);
            } else {
              boolean rootParameter = this.isRootParameter(rootAnnotationClassName, paramAnnotationClassName, parameter, parameterSize);
              if (!rootParameter) {
                String tempSearch = (String)searchStrsQueue.peek();
                String path = beetlMapperService.getParamAnnotationValue(paramAnnotationClassName, parameter);
                if (StringUtils.isBlank(path)) {
                  path = parameter.getName();
                }

                if (StrUtil.searchCharSequence(path, tempSearch)) {
                  child.setType(parameter.getType());
                  child.setPath(path);
                  child.setPsiElement(parameter.getOriginalElement());
                  child.setShow(true);
                  searchStrsQueue.poll();
                }
              }

              this.dsfParameterTypeFields(pageQueryClassName, child, parameter, searchStrsQueue);
            }
          }
        }
      }

      this.handleTreeNodeToLookupElement(result, root);
    }

    private boolean isRootParameter(String rootAnnotationClassName, String paramAnnotationClassName, PsiParameter parameter, int parameterSize) {
      if (!StringUtil.isEmptyOrSpaces(paramAnnotationClassName)) {
        return false;
      } else if (parameterSize == 1) {
        return true;
      } else {
        PsiAnnotation root = parameter.getAnnotation(rootAnnotationClassName);
        return root != null;
      }
    }

    private void addPrimitiveParameter(TreeNode child, PsiParameter parameter, String search) {
      BeetlMapperService beetlMapperService = BeetlMapperService.getInstance(parameter.getProject());
      String text = beetlMapperService.getParamAnnotationValue(parameter);
      if (StringUtils.isBlank(text)) {
        text = parameter.getName();
      }

      if (StrUtil.searchCharSequence(text, search)) {
        child.setType(parameter.getType());
        child.setPath(parameter.getName());
        child.setPsiElement(parameter.getOriginalElement());
        child.setShow(true);
      }

    }

    private void dsfParameterTypeFields(String pageQueryClassName, TreeNode child, PsiParameter parameter, Queue<String> searchStrQueue) {
      String parameterClassName = (String)Optional.ofNullable(PsiUtil.resolveClassInType(parameter.getType())).map(PsiClass::getQualifiedName).orElse("");
      PsiClass entityPsiClass;
      if (StringUtils.equals(pageQueryClassName, parameterClassName)) {
        PsiTypeElement[] typeParameterElements = (PsiTypeElement[])Optional.ofNullable(parameter.getTypeElement()).map(PsiTypeElement::getInnermostComponentReferenceElement).map(PsiJavaCodeReferenceElement::getParameterList).map(PsiReferenceParameterList::getTypeParameterElements).orElse(new PsiTypeElement[0]);
        PsiType entityType = (PsiType)Arrays.stream(typeParameterElements).findFirst().map(PsiTypeElement::getType).orElse(null);
        entityPsiClass = PsiUtil.resolveClassInType(entityType);
      } else {
        entityPsiClass = PsiUtil.resolveClassInType(parameter.getType());
      }

      PsiField[] fields = (PsiField[])Optional.ofNullable(entityPsiClass).map(PsiClass::getFields).orElse(new PsiField[0]);
      if (!ArrayUtils.isEmpty(fields)) {
        this.resolvePsiClass(child, Arrays.asList(fields), searchStrQueue);
      }
    }

    private void resolvePsiClass(TreeNode topNode, List<PsiField> fields, Queue<String> searchQueue) {
      String searchStr = (String)searchQueue.poll();
      if (!StringUtils.isBlank(searchStr)) {
        for(PsiField field : fields) {
          if (StrUtil.searchCharSequence(field.getName(), searchStr)) {
            TreeNode node = new TreeNode();
            node.setPsiElement(field.getOriginalElement());
            node.setPath(field.getName());
            node.setShow(true);
            node.setType(field.getType());
            topNode.addChild(node);
            PsiType fieldType = field.getType();
            if (!PsiKt.isPrimitive(fieldType)) {
              PsiClass fieldPsiCls = PsiUtil.resolveClassInType(fieldType);
              PsiField[] fieldPsiClsFields = (PsiField[])Optional.ofNullable(fieldPsiCls).map(PsiClass::getFields).orElse(new PsiField[0]);
              this.resolvePsiClass(node, Arrays.asList(fieldPsiClsFields), searchQueue);
            }
          }
        }

      }
    }

    private void handleTreeNodeToLookupElement(CompletionResultSet result, TreeNode treeNode) {
      this.dsfTreeNode(result, treeNode.getChildren(), "");
    }

    private void dsfTreeNode(CompletionResultSet result, List<TreeNode> children, String lookupStr) {
      for(TreeNode child : children) {
        String tempLookupStr = lookupStr + child.getPath() + ".";
        List<TreeNode> childChildren = child.getChildren();
        if (!childChildren.isEmpty()) {
          this.dsfTreeNode(result, childChildren, tempLookupStr);
        } else if (child.isShow()) {
          if (tempLookupStr.startsWith(".")) {
            tempLookupStr = tempLookupStr.substring(1);
          }

          if (tempLookupStr.endsWith(".")) {
            tempLookupStr = tempLookupStr.substring(0, tempLookupStr.length() - 1);
          }

          LookupElement lookupElement = LookupElementBuilder.createWithSmartPointer(tempLookupStr, child.getPsiElement()).withPresentableText(tempLookupStr).withCaseSensitivity(true).withIcon(PlatformIcons.VARIABLE_ICON).withItemTextForeground(JBColor.BLACK).bold().withTypeText(child.getType().getCanonicalText(), PlatformIcons.VARIABLE_ICON, true).withTypeIconRightAligned(true).withAutoCompletionPolicy(AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE);
          logger.info("【beetlsql】CompletionResultSet " + lookupElement.getLookupString());
          result.addElement(lookupElement);
        }
      }

    }
  }
}
